package com.corewillsoft.fireflies.gameobjects.obstacles.trajectories;

import com.corewillsoft.fireflies.util.MathUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * Class which implements calculations for polygonal trajectory of particular object
 */
public class PolylineTrajectory extends Trajectory {
	private static final int MIN_TRAJECTORY_PARAMETERS_COUNT = 5;	//value of initial polyline trajectory + 2 points (having x- and y-coordinates)

	/**
	 * Number of total points of polygonal line
	 */
	private int pointsCount = 0;

	/**
	 * List of x-coordinates of polygonal line
	 */
	private List<Float> trajectoryPointsX;

	/**
	 * List of y-coordinates of polygonal line
	 */
	private List<Float> trajectoryPointsY;

	/**
	 * X-coordinate of vector connecting current position of moving object with next point of polygonal line
	 */
	private Float currentTrajectoryVectorX;

	/**
	 * Y-coordinate of vector connecting current position of moving object with next point of polygonal line
	 */
	private Float currentTrajectoryVectorY;

	/**
	 * Index of segment of polygonal line where object is moving on now
	 */
	private int currentSegment = 0;

	/**
	 * Constructor of class
	 * @param parameters - list of float parameters, must contain odd number of parameters (minimum 5):
	 * <ul>
	 * <li>0 - number of initial segment of polyline where object is moving on (first segment has number 0)</li>
	 * <li>1 - x coordinate of first point of polyline</li>
	 * <li>2 - y coordinate of first point of polyline</li>
	 * <li>...</li>
	 * <li>2*N-1 - x coordinate of N-th point of polyline</li>
	 * <li>2*N - y coordinate of N-th point of polyline</li>
	 * </ul>
	 */
	public PolylineTrajectory(List<Float> parameters) {
		super(parameters);

		int parametersCount = parameters.size();
		if ((parametersCount < MIN_TRAJECTORY_PARAMETERS_COUNT) || (parametersCount % 2 == 0)) {
			return;
			//throw new RuntimeException("Wrong parameters count provided to RectangularTrajectory: expected " + TRAJECTORY_PARAMETERS_COUNT + ", but provided " + parametersCount);
		}

		pointsCount = (parametersCount - 1) / 2;

		//initialization of arrays of polyline points coordinates from parameters list
		trajectoryPointsX = new ArrayList<Float>(pointsCount);
		trajectoryPointsY = new ArrayList<Float>(pointsCount);

		for (int i = 1; i < parametersCount; i += 2) {
			trajectoryPointsX.add(parameters.get(i));
			trajectoryPointsY.add(parameters.get(i + 1));
		}

		//get current segment index from parameters list
		currentSegment = parameters.get(0).intValue();

		//initialization of trajectory vector
		currentTrajectoryVectorX = getCurrentSegmentEndX() - getCurrentSegmentStartX();
		currentTrajectoryVectorY = getCurrentSegmentEndY() - getCurrentSegmentStartY();
	}

	/**
	 * Calculates X- and Y- components of speed of object bound to this trajectory;
	 * object moves along polyline trajectory determined by parameters passed to constructor of class,
	 * to get calculated speed components, one should call {@link com.corewillsoft.fireflies.gameobjects.obstacles.trajectories.Trajectory#getSpeedX()} and {@link com.corewillsoft.fireflies.gameobjects.obstacles.trajectories.Trajectory#getSpeedY()}
	 * after calling of this method
	 * @param x - current X-coordinate of the moving object
	 * @param y - current Y-coordinate of the object
	 * @param maxSpeed - maximal absolute value of speed of the moving object
	 */
	@Override
	public void calculateSpeed(float x, float y, float maxSpeed) {
		/*
		check if object moved out from current segment,
		so it should start moving in the next segment (to the next polyline point)
		 */
		if (isCurrentSegmentEnded(x, y)) {
			int newCurrentSegment = currentSegment + 1;
			if (newCurrentSegment >= pointsCount) {		//after last segment, object should move in the first segment again
				newCurrentSegment = 0;
			}
			setCurrentSegment(newCurrentSegment);
		}

		float endPointX = getCurrentSegmentEndX();
		float endPointY = getCurrentSegmentEndY();

		//calculation of current movement vector, according to the current object position
		currentTrajectoryVectorX = endPointX - x;
		currentTrajectoryVectorY = endPointY - y;

		double trajectoryVectorLength = MathUtils.vectorLength(currentTrajectoryVectorX, currentTrajectoryVectorY);

		/*
		calculation of speed vector components, normilizing it to the maxSpeed value,
		so the length of speed vector will be always equal to the maxSpeed (uniform motion)
		 */
		speedX = (float) (currentTrajectoryVectorX * maxSpeed / trajectoryVectorLength);
		speedY = (float) (currentTrajectoryVectorY * maxSpeed / trajectoryVectorLength);
	}

	/**
	 * Checks if object moved out from the current polyline segment
	 * @param x - current x-coordinate of moving object
	 * @param y - current y-coordinate of moving object
	 * @return true, if object is out of current polyline segment; false otherwise
	 */
	private boolean isCurrentSegmentEnded(float x, float y) {
		float endPointX = getCurrentSegmentEndX();
		float endPointY = getCurrentSegmentEndY();

		/*
		flags, which are true if object position satisfies condition by X and by Y coordinates
		that it is out of current polyline segment
		*/
		boolean endCriteriaByX;
		boolean endCriteriaByY;

		if (currentTrajectoryVectorX == 0) {			//object is moving strictly up or down, so we don't care about its X coordinate
			endCriteriaByX = true;
		} else if (currentTrajectoryVectorX > 0) {		//object is moving to the right, so X should be larger than endpoint's X to exit segment
			endCriteriaByX = (x >= endPointX);
		} else {										//object is moving to the left, so X should be smaller than endpoint's X to exit segment
			endCriteriaByX = (x <= endPointX);
		}

		if (currentTrajectoryVectorY == 0) {			//object is moving strictly left or right, so we don't care about its Y coordinate
			endCriteriaByY = true;
		} else if (currentTrajectoryVectorY > 0) {		//object is moving up, so Y should be larger than endpoint's Y to exit segment
			endCriteriaByY = (y >= endPointY);
		} else {                                        //object is moving down, so Y should be smaller than endpoint's Y to exit segment
			endCriteriaByY = (y <= endPointY);
		}

		return (endCriteriaByX && endCriteriaByY);
	}

	/**
	 * Returns X coordinate of start point of current polyline segment where object is moving on
	 * @return X coordinate of start point of current segment
	 */
	private float getCurrentSegmentStartX() {
		return trajectoryPointsX.get(currentSegment);
	}

	/**
	 * Returns Y coordinate of start point of current polyline segment where object is moving on
	 * @return Y coordinate of start point of current segment
	 */
	private float getCurrentSegmentStartY() {
		return trajectoryPointsY.get(currentSegment);
	}

	/**
	 * Returns X coordinate of end point of current polyline segment where object is moving on
	 * @return X coordinate of end point of current segment
	 */
	private float getCurrentSegmentEndX() {
		if (currentSegment < pointsCount - 1) {
			return trajectoryPointsX.get(currentSegment + 1);
		} else {
			return trajectoryPointsX.get(0);
		}
	}

	/**
	 * Returns Y coordinate of end point of current polyline segment where object is moving on
	 * @return Y coordinate of end point of current segment
	 */
	private float getCurrentSegmentEndY() {
		if (currentSegment < pointsCount - 1) {
			return trajectoryPointsY.get(currentSegment + 1);
		} else {
			return trajectoryPointsY.get(0);
		}
	}

	/**
	 * Sets new value of current polyline segment index,
	 * informs parametersChangedDelegate about this change of the parameter
	 * @param currentSegment - value of current polyline segment index
	 */
	private void setCurrentSegment(int currentSegment) {
		this.currentSegment = currentSegment;
		if (obstacle != null) {
			obstacle.parameterChanged(0, (float) this.currentSegment);
		}
	}
}
